#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <math.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <stdarg.h>
#include <sys/epoll.h>

#include "hzqSocket.h"
#include "hzqFormat.h"

const unsigned int maxMessageSize=1024; //允许发送的最大消息长度
const unsigned int maxFileNameLen=256;  //最大文件长度
unsigned int current_connect=0;
static const int s_listenEq = 1024;//最大等待连接队列
static int g_file_fd = -1;     //接收文件句柄
/**
 * @brief 创建套接字并加入epoll
 * @param 
 *      servfd  作为结果参数，返回接听套接字描述符
 *      port    需要监听的端口号
 * @retval 
 *      成功返回 epoll描述符
 *      失败返回-1
 */
int creatEpollServer(int *servfd,short port)
{
    assert(servfd!=NULL);
    /* 1.创建套接字 */
    *servfd=socket(AF_INET,SOCK_STREAM,0);
    if(*servfd == -1){
        perror("socket");
        return -1;
    }
    /* 2.设置服务端地址 */
    struct sockaddr_in servAddr;
    socklen_t addrlen = sizeof(servAddr);
    bzero(&servAddr,addrlen);
    //bzero(&clientAddr,addrlen);
    servAddr.sin_family=AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(port);

    /* 3.bind地址 */
    int err = bind(*servfd,(struct sockaddr *)&servAddr,addrlen);
    if(err == -1){
        perror("bind");
        return -1;
    }

    /* 4.listen,服务器变为LISTEN状态 */
    err = listen(*servfd,s_listenEq);
    if(err == -1){
        perror("listen");
        return -1;
    }
    /* 5.创建epfd描述符 */
    int epfd = epoll_create(1);
    if(epfd == -1){
        perror("epoll_create");
        return -1;
    }
    /* 6.添加事件 */
    struct epoll_event ev;
    bzero(&ev,sizeof(ev));
    ev.data.fd = *servfd;
    ev.events = EPOLLIN ;//| EPOLLET;//可读事件(新的连接),边沿触发

    err = epoll_ctl(epfd,EPOLL_CTL_ADD,*servfd,&ev);
    if(err == -1){
        perror("epoll_ctrl");
        return -1;
    }
    return epfd;
}
/**
 * @brief   接收一个新的连接,加入epfd监听的队列
 * @param
 *      epfd    需要加入的epoll描述符
 *      serfd   监听描述符
 * @retval
 *      成功返回客户端描述符,失败返回-1
 */
int epollAccept(int epfd,int servfd){

    struct sockaddr_in clientAddr;
    socklen_t addrlen = sizeof(clientAddr);
    bzero(&clientAddr,addrlen);
    struct epoll_event ev;

    int clientfd = accept(servfd,(struct sockaddr *)&clientAddr,&addrlen);
    if(clientfd == -1){
        perror("accept");
        return -1;
    }else{
        char *str = inet_ntoa(clientAddr.sin_addr);
        printf("new connection:%s,port = %d,fd = %d!\n",str,ntohs(clientAddr.sin_port),clientfd);
        current_connect+=1;
        
        ev.data.fd = clientfd;
        ev.events = EPOLLRDHUP |EPOLLIN;//| EPOLLET;//这里一定不能设置成边沿触发，不然缓冲区满了之后触发不了读事件
        epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
    }
    return clientfd;
}
/** 
 * @brief  创建套接字并与服务器建立TCP连接
 * @note   
 * @param  *ip: 
 * @param  port: 
 * @retval 成功返回套接字描述符.失败返回-1
 */
int connect_serv(const char const *ip,short port)
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    assert(sockfd != -1);

    struct sockaddr_in clientAddr;
    socklen_t addrlen = sizeof(clientAddr);
    bzero(&clientAddr,addrlen);
    clientAddr.sin_family=AF_INET;
    clientAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    clientAddr.sin_port = 0;
    int err = bind(sockfd,(struct sockaddr*)&clientAddr,addrlen);//不绑定也行的
    assert(err != -1);

    clientAddr.sin_addr.s_addr = inet_addr(ip);
    clientAddr.sin_port = htons(port);
    err = connect(sockfd,(struct sockaddr*)&clientAddr,addrlen);
    if(err == -1){
        printf("ip:%s,port:%d\n",ip,port);
        perror("connect server");
        exit(0);
    }

    return sockfd;
}

/**
 * @brief  创建TCP服务，默认本地ip
 * @note   
 * @param  port: 端口号
 * @retval 
 */
int creatTcpServer(short port)
{
    int sock_fd=-1;

    /* 1.创建套接字 */
    sock_fd=socket(AF_INET,SOCK_STREAM,0);
    assert(sock_fd!=-1);

    /* 2.设置地址 */
    struct sockaddr_in addr;
    bzero(&addr,sizeof(addr));
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=htonl(INADDR_ANY);
    addr.sin_port=htons(port);

    //printf("local %s,port:%d\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
    

    /* 3.绑定地址 */
    int err = bind(sock_fd,(struct sockaddr *)&addr,sizeof(addr));
    if(err==-1){
        perror("bind ");
        exit(0);
    }

    /* 4.启动监听 */
    err = listen(sock_fd,5);
    if(err==-1){
        perror("listen ");
        exit(0);
    }

    /* 5.设置子程序退出信号处理函数 */
    signal(SIGCHLD,sig_chld);

    return sock_fd;
}

/**
 * @brief  子程序退出处理函数，防止出现僵尸进程
 * @note   
 * @param  signo: 
 * @retval None
 */
void sig_chld(int signo)
{
    pid_t pid;
    int stat;
    pid=wait(&stat);

    printf("child %d terminated,stat :%d\n",pid,stat);
}

/**
 * @brief   每次有新的连接就创建新的进程来执行这个函数
 *          函数将阻塞方式循环读这个句柄直到接收结束命令
 * @note   
 * @param  sockfd:  套接字句柄
 * @retval None
 */
int client_handle(int sockfd)
{
    char buf[maxMessageSize];
    int count=0;
    while(1){
        bzero(buf,maxMessageSize);
        count = read(sockfd, buf, Msg.m_msgLen);
        //printf("count = %d\n",count);

        /* 1.对端发送FIN后，还向这个套接字写会收到 RST */
        if ( count < 0) {   //读开头6个字节
            perror("read sockfd");
            if(sockfd > 2)
                close(sockfd);
            printf("host2-> read %d bytes\n",count);
            exit(-2);
        }
        /* 2.对方发送了FIN,服务器读会返回0，应答后处于CLOSE_WAIT状态 */
        else if (count == 0){
            printf("host2-> read 0 bytes from sockfd %d\n",sockfd);
            //printf("a user closed\n");
            close(sockfd);
            exit(-3);
        }
        /* 3.没有读到6个字节 */
        else if(count < 6){
            printf("messages is less than 6 bytes\n");
            exit(-1);
        }

        /* 4.正常读数据 */
        unsigned short cmd_num = 0;
        unsigned int packet_len = 0;
        head_analyze(buf,&cmd_num,&packet_len);
        //count = Read(sockfd, buf, packet_len);              //继续读文件内容
        if(packet_len > 1024){
            printf("error: cmd_num = %d,packet_len = %d,buffer %d\n",cmd_num,packet_len,(int)Read(sockfd, buf, maxMessageSize));
            printf("buf:%s\n",buf);
            exit(-1);
        }

        bzero(buf,maxMessageSize);
        count = 0;
        while(count < packet_len){
            count += Read(sockfd, buf+count, packet_len-count);              //继续读文件内容
        }
        delServerRecv(sockfd,cmd_num,packet_len,buf);
    }
    printf("exit normal\n");
    exit(0);
}
/** 
 * @brief  处理服务端接收的套接字数据
 * @note   
 * @param  cmd: 
 * @param  packet_len: 
 * @param  *buf: 
 * @retval 
 */
int delServerRecv(int sockfd,unsigned short cmd,unsigned int packet_len,char *buf)
{
    //printf("cmd num = %d\n",cmd);
    switch (cmd){
        case e_msgDebug:
            printf("recv message: %s",buf);
            break;
        case e_msgEndFile:              //正常结束进程
            package_head((unsigned char *)buf,e_msgEndFile,0);
            Write(sockfd,buf,6);
            close(sockfd);
            printf("end of the file\n");
            exit(12);
            break;
        case e_msgFileContent:          //保存文件内容
            saveFileContent(buf,packet_len);
            break;
        case e_msgSendFile:             //根据文件名创建文件句柄
            /* 客户端发送文件名 */
            handle_file_head(buf);
            break;
        default:
            printf("unknow cmd\n");
            return -1;
    }
    return 0;
}
void saveFileContent(char *buf,int len)
{
    if(g_file_fd==-1){
        printf("file is not open!!!\n");
        exit(-1);
    }
    lseek(g_file_fd,0,SEEK_END);
    Write(g_file_fd,buf,len);
}
void handle_file_head(char *buf)
{
    g_file_fd = open(buf, O_WRONLY|O_CREAT|O_TRUNC,FILE_MODE);
    if(g_file_fd == -1){
        if(errno==EEXIST){
            printf("file %s has allready exist",buf);
            exit(-5);
        }else{
            printf("host2->when open %s ",buf);
            perror("");
        }
        exit(-6);
    }
}
void Write(int fd, void *ptr, size_t nbytes)
{
	int r=write(fd, ptr, nbytes);
    if ( r!= nbytes){
		printf("error: write to fd %d: %d %d\n",fd,r,(int)nbytes);
    }
}
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t		n=0;

	if ( (n = read(fd, ptr, nbytes)) == -1){
		perror("read error");
    }
	return(n);
}